<? 
/****************************************************************************
 * (c) 2003 Jonathan Haskins <jhaskins@killobyte.com>
 * <place copyright notice here for each person who contributes to this file>
 *
 * TERMS OF USAGE:
 * This file is part of Web://CP.
 *
 * Web://CP is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 ****************************************************************************/

/* $Id: server.inc.phps,v 1.9 2007/02/28 23:10:58 gyrbo Exp $ */

/*
 * File:	server.inc.phps
 * Purpose:	api for handling the server threads
 * Creation:	2004-01-14
 */

// start a server that listens on a socket and repeatedly checks for new connections and collects
// data on each socket. Keep open connections in an array and cycle through them using a callback
// function to enable different protocols.
function run_server($function, $max_clients=20, $timeout=100, $keep_alive=true, $keep_alive_timeout=15, $ssl=false) {

	global $lsocket, $csocket, $cdata, $echo;

	/* enter an endless loop */
	while (true) {

		// php 4.3 pcntl doesn't work without ticks declaration
		// we will catch all signals here
		declare(ticks=1) {
			// we need to perform any operation
			$temp = null;
		}

		/* close any sockets that have timed out */
		foreach($csocket as $slot => $socket) {
			if (!empty($timeout) && (get_microtime() - $cdata[$slot]['established']) > $timeout) {
				close_client($socket);
			} elseif ($keep_alive && !$ssl) {
				if (get_microtime() > $cdata[$slot]['keep_alive']) {
					close_client($socket);
				}
			}
		}

		/* build array consisting of sockets we want to get updates for */
		$set_r = array($lsocket);
		$set_r = array_merge($set_r, $csocket);

		/* $set_r will be an array holding the sockets that we have received data on.
		 * $ready will be the number of sockets that we have recieved data on. */
		$ready = @socket_select($set_r, $set_w = null, $set_e = null, $to_sec = 1);
		if ($ready == 0 || $ready === false) {
			/* there are zero updates on the listening socket, or we caught a posix signal */
	 		continue;
		}

		/* if there is an update on the listening socket create a new connection */
		if (in_array($lsocket, $set_r)) {
			open_client($lsocket, $max_clients);
		}

		/* start loop over if there isn't anymore updated sockets */
		if ($ready-- <= 0) {
			continue;
		}

		/* otherwise check client sockets for incoming data */
		foreach ($csocket as $slot => $socket) {
			if (!in_array($socket, $set_r)) {
				continue;
			}

			/* read data from the client into a buffer. if read fails close the connection */
			if (($tmpbuf = @socket_read($socket, 4096))===false) {
				close_client($socket);
				webcp_log(2, 0, $function, 'Failed: '.socket_strerror(socket_last_error($lsocket)), 0, $echo);
			} else {
				/*if(strlen($tmpbuf)<1) {
					//just something to show up in strace
					stat('/var/log/webcp');
				}*/

				$cdata[$slot]['buffer'] .= $tmpbuf;
				/* since we got a response, renew our keepalive timeout */
				if ($keep_alive && !$ssl) {
					$cdata[$slot]['keep_alive'] = get_microtime() + $keep_alive_timeout;
				}

				// Run our callback function which will handle any protocol specific
				// events we need for the type of server we want to run
				$function($socket, $cdata[$slot]);
			}

			/* stop checking the client sockets if there isn't anymore updated sockets */
			if ($ready-- <= 0) {
				break;
			}
		}
	}
}

//doesn't work yet! not used!
function preforking_server($function, $max_clients=20, $timeout=100, $keep_alive=true, $keep_alive_timeout=15, $ssl=false) {
	global $lsocket, $csocket, $cdata, $echo, $cfg, $children, $childpid;
	$children = 0;
	$childpid = Array();

	// Ensure children are exited when they die
	pcntl_signal(SIGCHLD, 'child_exit');
	pcntl_signal(SIGTERM, 'children_kill');

	/* enter an endless loop */
	while (true) {

		// php 4.3 pcntl doesn't work without ticks declaration
		// we will catch all signals here
		declare(ticks=1) {
			sleep(1);
		}

		if ($children < $cfg['httpd_children']) {

			if (($pid = pcntl_fork()) < 0) {
				echo "Fork Error: could not fork webcp-httpd.php";
			} elseif ($pid == 0) {

				// Set child sigterm
				pcntl_signal(SIGTERM, 'child_die');

				// Set child sighup
				pcntl_signal(SIGHUP, 'child_reload');

				/* initialize the client socket array */
				$csocket = array();

				/* we are the child, enter an endless loop */
				while (true) {

					// catch signals here
					declare(ticks=1) {
						// we need to perform any operation
						$temp = null;
					}

					/* close any sockets that have timed out */
					foreach($csocket as $slot => $socket) {
						if (isset($cdata[$slot])) {
							if (!empty($cfg['timeout']) && (get_microtime() - $cdata[$slot]['established']) > $cfg['timeout']) {
								close_client($socket);
							} elseif ($cfg['keep_alive'] && !$cfg['ssl']) {
								if (get_microtime() > $cdata[$slot]['keep_alive']) {
									close_client($socket);
								}
							}
						}
					}

					/* build array consisting of sockets we want to get updates for */
					$set_r = array($lsocket);
					$set_r = array_merge($set_r, $csocket);

					/* $set_r will be an array holding the sockets that we have received data on.
					 * $ready will be the number of sockets that we have recieved data on. */
					$ready = @socket_select($set_r, $set_w = null, $set_e = null, $to_sec = null);
					if ($ready == 0 || $ready === false) {
						/* there are zero updates on the listening socket, or we caught a posix signal */
				 		continue;
					}

					/* if there is an update on the listening socket create a new connection */
					if (in_array($lsocket, $set_r)) {
						open_client($lsocket, $max_clients);
					}

					/* start loop over if there isn't anymore updated sockets */
					if ($ready-- <= 0) {
						continue;
					}

					/* otherwise check client sockets for incoming data */
					foreach ($csocket as $slot => $socket) {
						if (!in_array($socket, $set_r)) {
							continue;
						}

						/* read data from the client into a buffer. if read fails close the connection */
						if (!$cdata[$slot]['buffer'] .= @socket_read($socket, MAX_LENGTH)) {
							close_client($socket);
						} else {
							/* since we got a response, renew our keepalive timeout */
							if ($cfg['keep_alive'] && !$cfg['ssl']) {
								$cdata[$slot]['keep_alive'] = get_microtime() + $cfg['keep_alive_timeout'];
							}

							// Set time limit (2 minutes)
							set_time_limit(120);

							// Run our callback function which will handle any protocol specific
							// events we need for the type of server we want to run
							$function($socket, $cdata[$slot]);

							// Reset time limit (unlimited)
							set_time_limit(0);
						}

						/* stop checking the client sockets if there isn't anymore updated sockets */
						if ($ready-- <= 0) {
							break;
						}
					}
				}
			} else {
				// We are the parent
				/* Increment total number of child processes */
				$children++;
				$childpid[$pid] = 1;
			}
		}
	}
}

function child_die() {
	exit;
}

function children_kill($signo='') {
	global $childpid;
	foreach($childpid as $pid => $status) {
		if ($status) {
			posix_kill($pid, SIGUSR1);
		}
	}
	exit;
}

//doesn't work yet! not used!
function child_exit($signo='') {
	global $children, $childpid;

	/* clean up after any defunct children  */
	while ($children > 0) {
		$pid = pcntl_waitpid(0, $status = null, WNOHANG);

		if ($pid === false) {
			webcp_log(1, 0, 'webcp-httpd', 'pcntl_waitpid() error!', 0, $echo);
		} elseif ($pid == 0) {
			/* no child to wait on */
			break;
		} elseif ($pid > 0) {
			/* a child exited, decrease the total number of child processes */
			unset($childpid[$pid]);
			$children--;
		}
	}
}

// open a socket to listen for connections on
function open_socket($port, $listen_address='0.0.0.0', $listen_queue=20) {

	// create a listening socket
	if (!$lsocket = @socket_create(AF_INET, SOCK_STREAM, 0)) {
		return $lsocket;
	}

	// able to reuse our listening socket
	if (!@socket_setopt($lsocket, SOL_SOCKET, SO_REUSEADDR, 1)) {
		return $lsocket;
	}

	// make our socket non-blocking
	if (!@socket_set_nonblock($lsocket)) {
		return $lsocket;
	}

	// bind the listening socket to the specified address and port
	if (!@socket_bind($lsocket, $listen_address, $port)) {
		return $lsocket;
	}

	// start listening on our socket
	if (!@socket_listen($lsocket, $listen_queue)) {
		return $lsocket;
	}

	return $lsocket;
}

// close listening socket and any children it has
function close_socket() {
	global $lsocket, $csocket, $echo;

	// shut down client sockets
	foreach ($csocket as $slot => $socket) {
		close_client($socket);
	}

	// shut down listening socket
	@socket_shutdown($lsocket, 2);
	@socket_close($lsocket);
}

// open a connection between the server and a client machine over a socket.
function open_client($lsocket, $max_clients) {

	global $cfg, $csocket, $cdata, $httpd_status, $echo;

	/* find an open slot and fill it with the new connection. */
	for ($slot = 1; $slot <= $max_clients; $slot++) {
		if (!isset($csocket[$slot])) {
			if (($csocket[$slot] = @socket_accept($lsocket))===false) {
				webcp_log(1,0,'webcp-httpd',"Client failed to connect because: ".socket_strerror(socket_last_error($lsocket)),0,$echo);
				return;
			}
			//$httpd_status['connections']++;

			/* get ip and port of the client and local server */
			
			if (!@socket_getpeername($csocket[$slot], $cdata[$slot]['remote_ip'], $cdata[$slot]['remote_port'])) {
				close_client($csocket[$slot]);
					echo 'closed';
				return;
			}
			if (!socket_getsockname($csocket[$slot], $cdata[$slot]['local_ip'], $cdata[$slot]['local_port'])) {
				close_client($csocket[$slot]);
					echo 'closed';
				return;
			}

			/* if using ssl we should only accept local connections from stunnel */
			if ($cfg['ssl']) {
				if ($cdata[$slot]['remote_ip'] != '127.0.0.1') {
					close_client($csocket[$slot]);
					echo 'closed';
					return;
				}
			}

			/* assign initial client data */
			$cdata[$slot]['established'] = get_microtime();
			if ($cfg['keep_alive'] && !$cfg['ssl']) {
				$cdata[$slot]['keep_alive'] = $cdata[$slot]['established'] + $cfg['keep_alive_timeout'];
			}
			$cdata[$slot]['buffer'] = '';

			webcp_log(3,0,'webcp-httpd',$cdata[$slot]['remote_ip']." connected to ".$cdata[$slot]['local_ip']." on port ".$cdata[$slot]['local_port']." at ".date("g:i:s a", $cdata[$slot]['established']),0,$echo);
			break;
		}
	}
}

// close a connection between the server and a client machine.
function close_client($socket) {

	global $csocket, $cdata, $echo;

	$slot = array_search($socket, $csocket);

	/* if still connected shutdown and close the socket */
	if (isset($csocket[$slot]) && is_resource($csocket[$slot])) {
		@socket_shutdown($csocket[$slot], 2);
		@socket_close($csocket[$slot]);
	} else {
		webcp_log(1,0,'webcp-httpd',"Socket not found while attempting to close the connection",0,$echo);
	}

	if (isset($csocket[$slot])) {
		unset($GLOBALS['csocket'][$slot]);
	}

	/* clean out data associated with the socket */
	if (isset($cdata[$slot])) {
		
		
		webcp_log(3,0,'webcp-httpd',(isset($cdata[$slot]['remote_ip'])?$cdata[$slot]['remote_ip']:'')." disconnected from ".(isset($cdata[$slot]['local_ip'])?$cdata[$slot]['local_ip']:'')." on port ".(isset($cdata[$slot]['local_port'])?$cdata[$slot]['local_port']:'')." at ".date("g:i:s a", get_microtime()),0,$echo);
		unset($GLOBALS['cdata'][$slot]);
	}
}

// create and handle pid file details
function set_pid_file($pid, $pid_file, $uid) {

	global $echo;

	/* check for an existing pid file */
	if (is_readable($pid_file) && !is_dir($pid_file)) {
		if ($fp = fopen($pid_file, 'r')) {
			$old_pid = fgets($fp, 4096);
			fclose($fp);
		}

		/* if there is a corresponding process, cancel execution. */
		if ($old_pid > 0 && posix_kill($old_pid, 0)) {
			return false;
		}
	}

	/* create a pid file */
	if ($fp = fopen($pid_file, 'w')) {
		fputs($fp, $pid);
		fflush($fp);
		fclose($fp);

		/* make sure we can delete the pid file later */
		chown($pid_file, $uid);
		chmod($pid_file, 0644);

	} else {
		return false;
	}

	return true;
}
